package com.yugao.fintech.draper.multicache.support;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.yugao.fintech.draper.message.queue.client.RedisMQTemplate;
import com.yugao.fintech.draper.multicache.properties.MultiCacheProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisCaffeineCacheManager implements CacheManager {

    private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>();

    private MultiCacheProperties multiCacheProperties;

    private RedisTemplate<String, Object> stringKeyRedisTemplate;

    private RedisMQTemplate redisMQTemplate;

    private boolean dynamic;

    private Set<String> cacheNames;

    public RedisCaffeineCacheManager(MultiCacheProperties multiCacheProperties,
                                     RedisTemplate redisTemplate, RedisMQTemplate redisMQTemplate) {
        this.multiCacheProperties = multiCacheProperties;
        this.stringKeyRedisTemplate = redisTemplate;
        this.redisMQTemplate = redisMQTemplate;
        this.dynamic = multiCacheProperties.isDynamic();
        this.cacheNames = multiCacheProperties.getCacheNames();
    }

    /**
     * 根据名称获取缓存
     *
     * @param name the cache identifier (must not be {@code null}) 缓存名称
     * @return 缓存
     */
    @Override
    public Cache getCache(String name) {
        Cache cache = cacheMap.get(name);
        if (cache != null) {
            return cache;
        }
        if (!dynamic && !cacheNames.contains(name)) {
            return cache;
        }

        cache = new RedisCaffeineCache(name, redisMQTemplate, stringKeyRedisTemplate, caffeineCache(), multiCacheProperties);
        Cache oldCache = cacheMap.putIfAbsent(name, cache);
        log.debug("create cache instance, the cache name is : {}", name);
        return oldCache == null ? cache : oldCache;
    }

    /**
     * 获取缓存名称集合
     *
     * @return 缓存名称集合
     */
    @Override
    public Collection<String> getCacheNames() {
        return this.cacheNames;
    }

    /**
     * 清空本地缓存
     */
    public void clearLocal(String cacheName, Object key) {
        Cache cache = null;

        if (StringUtils.hasText(cacheName)) {
            cache = cacheMap.get(cacheName);
        }
        if (Objects.isNull(cache)) {
            return;
        }

        RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
        redisCaffeineCache.clearLocal(key);
    }

    public com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache() {
        Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();

        // 访问后过期时间
        if (multiCacheProperties.getCaffeine().getExpireAfterAccess() > 0) {
            cacheBuilder.expireAfterAccess(multiCacheProperties.getCaffeine().getExpireAfterAccess(),
                    TimeUnit.MILLISECONDS);
        }

        // 写入后过期时间
        if (multiCacheProperties.getCaffeine().getExpireAfterWrite() > 0) {
            cacheBuilder.expireAfterWrite(multiCacheProperties.getCaffeine().getExpireAfterWrite(),
                    TimeUnit.MILLISECONDS);
        }

        // 初始化大小
        if (multiCacheProperties.getCaffeine().getInitialCapacity() > 0) {
            cacheBuilder.initialCapacity(multiCacheProperties.getCaffeine().getInitialCapacity());
        }

        // 最大缓存对象个数
        if (multiCacheProperties.getCaffeine().getMaximumSize() > 0) {
            cacheBuilder.maximumSize(multiCacheProperties.getCaffeine().getMaximumSize());
        }

        // 写入后刷新时间
        if (multiCacheProperties.getCaffeine().getRefreshAfterWrite() > 0) {
            cacheBuilder.refreshAfterWrite(multiCacheProperties.getCaffeine().getRefreshAfterWrite(),
                    TimeUnit.MILLISECONDS);
        }

        return cacheBuilder.build();
    }
}
